package model

import (
	"common/helper"
	myUserHelp "common/userHelp"
	"errors"
	"fmt"
	"io/ioutil"
	ryrpc "member/rpc"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/go-redis/redis/v8"
)

type callFunc func(string, string) error

var smscb = map[string]callFunc{
	"buka": smsBuka, // Mobile 短信验证码
	"kmi":  smsKmi,
}
var mailcb = map[string]callFunc{
	"gmail": gmail, // gmail发送邮件
	//"awsmail": awsMail, //aws发送邮件
}
var smsConf = map[string]map[string]string{
	"buka": map[string]string{
		"api":    "https://api.onbuka.com/v3/sendSms",
		"appid":  "Jbv5e74p",
		"key":    "6A3brH7P",
		"secret": "lJgbny8d",
	},
	"kmi": map[string]string{
		"api":    "http://api.kmicloud.com/sms/send/v1/otp",
		"key":    "2901849a7e944333897d199b42e8b771",
		"secret": "5fb171db52f14fdeb08c7edda3381edc",
		//"callbackUrl": "https://www.cyestari.com/sms/callback/kmi", // 可在短信商后台配置，这里优先级高于配置
		"text": "[ LB88 ] Código de verificação:",
	},
}

func SmsOffline(phone, day, ip, flags, ty string) (string, int64, error) {

	key := fmt.Sprintf("sms:%s", phone)
	cmd := meta.MerchantRedis.SetNX(ctx, key, "1", 300*time.Second)
	ok, err := cmd.Result()
	if err != nil {
		return "0", 0, pushLog(err, helper.RedisErr)
	}

	if !ok {
		return "0", 0, errors.New(helper.RequestBusy)
	}

	if ty == "1" {
		memberKey := "member:" + phone
		if meta.MerchantRedis.Exists(ctx, memberKey).Val() > 0 {
			return "0", 0, errors.New(helper.UsernameExist)
		}

		existKey := fmt.Sprintf("phoneExist")
		if meta.MerchantRedis.SIsMember(ctx, existKey, phone).Val() {
			_ = meta.MerchantRedis.Unlink(ctx, key).Err()
			return "0", 0, errors.New(helper.PhoneExist)
		}
	}
	code := rand(4)
	name, tunnel, err := isSmsQuota(day, ip, flags)
	if err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, err
	}

	if tunnel == "" {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, errors.New(helper.CateNotExist)
	}

	cb, ok := smscb[tunnel]
	if !ok {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, errors.New(helper.CateNotExist)
	}

	err = cb(phone, code)
	if err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, err
	}

	id := helper.GenId()
	if err := setSmsQuota(phone, day, ip, id, code); err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return id, 0, err
	}

	ts := time.Now()
	smsLogPush(id, ip, code, phone, name, ty, flags, ts)
	return id, ts.UnixMicro(), nil
}

func rand(n int) string {

	data := make([]string, n)

	for i := 0; i < n; i++ {
		data[i] = fmt.Sprintf("%d", meta.Rand.Intn(9))
	}

	return strings.Join(data, "")
}

func isSmsQuota(day, ip, flags string) (string, string, error) {

	key := fmt.Sprintf("sms:s:%s%s", ip, day)
	_, err := meta.MerchantRedis.Get(ctx, key).Result()
	if err != nil && err != redis.Nil {
		return "", "", errors.New(helper.RedisErr)
	}

	return "buka", "buka", nil
}

func setSmsQuota(phone, day, ip, id, code string) error {

	pipe := meta.MerchantRedis.Pipeline()
	defer pipe.Close()

	key := fmt.Sprintf("sms:s:%s%s", ip, day)
	codeKey := fmt.Sprintf("sms:%s%s", phone, id)
	pipe.Incr(ctx, key)
	pipe.Expire(ctx, key, 24*3600*time.Second)
	pipe.Set(ctx, codeKey, code, 5*time.Minute)

	helper.InfoLog("setSmsQuota:%s-%s", codeKey, code)
	_, err := pipe.Exec(ctx)
	if err != nil {
		return errors.New(helper.RedisErr)
	}

	return nil
}

func smsLogPush(id, ip, code, phone, tunnel, ty, flags string, ts time.Time) {

	recs := ryrpc.TblSmsLog{
		ID:        id,
		Ty:        "1",
		State:     "1",
		Source:    tunnel,
		IP:        ip,
		Phone:     phone,
		Flags:     flags,
		Code:      code,
		Ts:        ts.UnixMilli(),
		CreateAt:  ts.Unix(),
		UpdatedAt: ts.Unix(),
	}
	b, err := helper.JsonMarshal(recs)
	if err != nil {
		return
	}

	index := meta.Meili.Index("smslog")
	_, err = index.AddDocuments(b, "id")
	if err != nil {
		return
	}

	body := fmt.Sprintf("t=1&id=%s&uid=0", id)
	helper.InfoLog("smslog：%s", body)
	//_, _ = meta.MerchantBean.Put("smslog", []byte(body), 0, 0, 0)

	zinc := map[string]interface{}{
		"id":         id,
		"ty":         "1",
		"state":      "1",
		"source":     tunnel,
		"ip":         ip,
		"phone":      phone,
		"flags":      flags,
		"code":       code,
		"ts":         ts.UnixMilli(),
		"cost":       fmt.Sprintf("%v", time.Since(ts)),
		"create_at":  ts.Unix(),
		"updated_at": ts.Unix(),
		"_index":     fmt.Sprintf("sms_log_%04d%02d", ts.Year(), ts.Month()),
	}
	bz, _ := helper.JsonMarshal(zinc)
	helper.InfoLog("zinc_fluent_log：%s", string(bz))
	//_, _ = meta.MerchantBean.Put("zinc_fluent_log", bz, 0, 0, 0)
}

func CheckSmsCaptcha(ip, sid, phone, code string) error {

	key := fmt.Sprintf("sms:%s%s", phone, sid)
	cmd := meta.MerchantRedis.Get(ctx, key)
	val, err := cmd.Result()
	if err != nil && err != redis.Nil {
		_ = pushLog(err, helper.RedisErr)
		return errors.New(helper.PhoneVerificationErr)
	}

	if code == val {
		return nil
	}

	return errors.New(helper.PhoneVerificationErr)
}

func MailOffline(username, mail, day, ip, ty string) (string, int64, error) {

	ts := time.Now()
	key := fmt.Sprintf("mail:%s", mail)
	cmd := meta.MerchantRedis.SetNX(ctx, key, "1", 200*time.Second)
	ok, err := cmd.Result()
	if err != nil {
		return "0", 0, pushLog(err, helper.RedisErr)
	}

	if !ok {
		return "0", 0, errors.New(helper.RequestBusy)
	}

	// 绑定邮箱地址
	if ty == "1" {
		existKey := "mailExist"
		if meta.MerchantRedis.SIsMember(ctx, existKey, mail).Val() {
			_ = meta.MerchantRedis.Unlink(ctx, key).Err()
			return "0", 0, errors.New(helper.EmailExist)
		}
	}

	code := rand(4)
	name, tunnel, err := isEmailQuota(day, ip)
	if err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, err
	}

	if tunnel == "" {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, errors.New(helper.CateNotExist)
	}

	cb, ok := mailcb[tunnel]
	if !ok {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, errors.New(helper.CateNotExist)
	}

	err = cb(mail, code)
	if err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, err
	}

	id := helper.GenId()
	if err := setMailQuota(mail, day, ip, id, code); err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return id, 0, err
	}

	mailLogPush(id, username, ip, code, mail, name, ty, ts)
	return id, ts.UnixMicro(), nil
}

func isEmailQuota(day, ip string) (string, string, error) {

	key := fmt.Sprintf("mail:s:%s%s", ip, day)
	val, err := meta.MerchantRedis.Get(ctx, key).Result()
	if err != nil && err != redis.Nil {
		return "", "", errors.New(helper.RedisErr)
	}

	total := 0
	if v, err := strconv.Atoi(val); err == nil {
		total = v
	}

	helper.InfoLog("==== FROM IP:  [%s]  Total = %d\n", ip, total)

	//exceptIPs := "49.157.15.129," +
	//	"103.98.29.67," +
	//	"49.157.15.132," +
	//	"152.32.163.184," +
	//	"146.196.65.98" +
	//	"49.157.15.130" +
	//	"49.157.15.134" +
	//	"103.98.29.72" +
	//	"103.98.29.66" +
	//	"103.98.29.70"
	//b := strings.Contains(exceptIPs, ip)
	//if !b && total >= 10 {
	//	return "", "", errors.New(helper.VerificationNumberNull)
	//}

	return "gmail", "gmail", nil
}

func CheckEmailCaptcha(ip, sid, mail, code string) error {
	return nil
}

func mailLogPush(id, username, ip, code, mail, tunnel, ty string, ts time.Time) {

	recs := ryrpc.TblSmsLog{
		ID:        id,
		Ty:        "2",
		State:     "1",
		Source:    tunnel,
		IP:        ip,
		Phone:     mail,
		Flags:     "text",
		Code:      code,
		Ts:        ts.UnixMilli(),
		CreateAt:  ts.Unix(),
		UpdatedAt: ts.Unix(),
	}
	b, err := helper.JsonMarshal(recs)
	if err != nil {
		return
	}

	index := meta.Meili.Index("smslog")
	_, err = index.AddDocuments(b, "id")
	if err != nil {
		return
	}

	body := fmt.Sprintf("t=1&id=%s&uid=0", id)
	helper.InfoLog("smslog：%s", body)
	//_, _ = meta.MerchantBean.Put("smslog", []byte(body), 0, 0, 0)

	zinc := map[string]interface{}{
		"id":        id,
		"username":  username,
		"ty":        "2",
		"state":     "1",
		"source":    tunnel,
		"ip":        ip,
		"phone":     mail,
		"flags":     "text",
		"code":      code,
		"ts":        ts.UnixMilli(),
		"cost":      fmt.Sprintf("%v", time.Since(ts)),
		"createAt":  ts.Unix(),
		"updatedAt": ts.Unix(),
		"_index":    fmt.Sprintf("mail_log_%04d%02d", ts.Year(), ts.Month()),
	}
	bz, _ := helper.JsonMarshal(zinc)
	helper.InfoLog("zinc_fluent_log：%s", string(bz))
	//_, _ = meta.MerchantBean.Put("zinc_fluent_log", bz, 0, 0, 0)
}

func gmail(mail, code string) error {

	port, _ := strconv.Atoi(meta.Email.Port)
	err := SendMail(meta.Email.Username, mail, meta.Email.URL, port, meta.Email.Password, "LB88!", code)
	if err != nil {
		_ = pushLog(err, helper.ServerErr)
	}

	return err
}

func awsMail(mail, code string) error {

	err := awsSendMail(mail, code, "LB88!")
	if err != nil {
		_ = pushLog(err, helper.ServerErr)
	}

	return err
}

func setMailQuota(mail, day, ip, id, code string) error {

	pipe := meta.MerchantRedis.TxPipeline()
	defer pipe.Close()

	key := fmt.Sprintf("mail:s:%s%s", ip, day)
	codeKey := fmt.Sprintf("mail:%s%s", mail, id)
	pipe.Incr(ctx, key)
	pipe.Expire(ctx, key, 24*3600*time.Second)
	pipe.Set(ctx, codeKey, code, 5*time.Minute)

	_, err := pipe.Exec(ctx)
	if err != nil {
		return errors.New(helper.RedisErr)
	}

	return nil
}

func SmsOnline(uid string, day, ip, flags, ty, phone string) (string, int64, error) {

	if ty != "5" {
		phone = "55" + myUserHelp.GetMemberPhone(uid)
	}

	key := fmt.Sprintf("sms:%s", phone)
	cmd := meta.MerchantRedis.SetNX(ctx, key, "1", 300*time.Second)
	ok, err := cmd.Result()
	if err != nil {
		return "0", 0, pushLog(err, helper.RedisErr)
	}

	if !ok {
		return "0", 0, errors.New(helper.RequestBusy)
	}

	if len(phone) < 9 {
		return "0", 0, errors.New(helper.PhoneFMTErr)
	}

	code := rand(4)
	name, tunnel, err := isSmsQuota(day, ip, flags)
	if err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, err
	}

	if tunnel == "" {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, errors.New(helper.CateNotExist)
	}

	cb, ok := smscb[tunnel]
	if !ok {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, errors.New(helper.CateNotExist)
	}

	err = cb(phone, code)
	if err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, err
	}

	id := helper.GenId()
	if err := setSmsQuota(phone, day, ip, id, code); err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return id, 0, err
	}

	ts := time.Now()
	smsLogPush(id, ip, code, phone, name, ty, flags, ts)

	return id, ts.UnixMicro(), nil
}

func MailOnline(uid string, email, day, ip, ty string) (string, int64, error) {

	ts := time.Now()
	if ty != "2" {
		email = myUserHelp.GetMemberEmail(uid)
	}

	key := fmt.Sprintf("mail:%s", email)
	cmd := meta.MerchantRedis.SetNX(ctx, key, "1", 200*time.Second)
	ok, err := cmd.Result()
	if err != nil {
		return "0", 0, pushLog(err, helper.RedisErr)
	}

	if !ok {
		return "0", 0, errors.New(helper.RequestBusy)
	}

	if !strings.Contains(email, "@") {
		return "0", 0, errors.New(helper.PhoneFMTErr)
	}

	code := rand(4)
	name, tunnel, err := isEmailQuota(day, ip)
	if err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, err
	}

	if tunnel == "" {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, errors.New(helper.CateNotExist)
	}

	cb, ok := mailcb[tunnel]
	if !ok {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, errors.New(helper.CateNotExist)
	}

	err = cb(email, code)
	if err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return "0", 0, err
	}

	id := helper.GenId()
	if err := setMailQuota(email, day, ip, id, code); err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return id, 0, err
	}

	mailLogPush(id, myUserHelp.GetMemberName(uid), ip, code, email, name, ty, ts)
	return id, ts.UnixMicro(), nil
}

func SendSms(phone, day, ip, flags, ty string) (string, int64, error) {

	key := fmt.Sprintf("sms:%s", phone)
	cmd := meta.MerchantRedis.SetNX(ctx, key, "1", 300*time.Second)
	ok, err := cmd.Result()
	if err != nil {
		helper.GetloggerInstance().Criticalf("%s---%d", helper.RedisErr, time.Now().Unix())
		return "0", 0, pushLog(err, helper.RedisErr)
	}

	if !ok {
		helper.GetloggerInstance().Criticalf("%s---%d", helper.RequestBusy, time.Now().Unix())
		return "0", 0, errors.New(helper.RequestBusy)
	}

	code := rand(4)
	message := fmt.Sprintf("Prezado cliente, Seu código de verificação é %s. Obrigado!", code)
	smsConfig := SMSConfig{
		AppKey:    "bx6688",
		AppSecret: "W6ouW4",
		APIURL:    "http://47.242.85.7:9090/sms/batch/v2",
	}

	if err := sendSMSCode(smsConfig, phone, message); err != nil {
		helper.GetloggerInstance().Infof("Failed to send SMS:", err)
	}

	id := helper.GenId()
	if err := setSmsQuota(phone, day, ip, id, code); err != nil {
		_ = meta.MerchantRedis.Unlink(ctx, key).Err()
		return id, 0, err
	}

	ts := time.Now()
	smsLogPush(id, ip, code, phone, "batch", ty, flags, ts)

	return id, ts.UnixMicro(), nil
}

type SMSConfig struct {
	AppKey    string `json:"appkey"`
	AppSecret string `json:"appsecret"`
	APIURL    string `json:"apiurl"`
}

func sendSMSCode(config SMSConfig, mobile, message string) error {
	//message = url.QueryEscape(message)
	params := url.Values{}
	params.Set("appkey", config.AppKey)
	params.Set("appsecret", config.AppSecret)
	params.Set("phone", mobile)
	params.Set("msg", message)
	params.Set("appcode", "1000")
	apiURL := config.APIURL
	apiURL += "?" + params.Encode()

	// Send HTTP request
	resp, _ := http.Get(apiURL)
	defer resp.Body.Close()

	// Read response body
	ioutil.ReadAll(resp.Body)
	return nil
}
